﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Threading;
using Aeon.Emulator.Decoding;

namespace Aeon.Emulator.Launcher
{
    /// <summary>
    /// Instruction profiler window.
    /// </summary>
    public partial class InstructionProfilerWindow : Window
    {
        private readonly List<StatisticMonitor> opcodes = new List<StatisticMonitor>();
        private readonly DispatcherTimer timer;

        /// <summary>
        /// Initializes a new instance of the <see cref="InstructionProfilerWindow"/> class.
        /// </summary>
        public InstructionProfilerWindow()
        {
            timer = new DispatcherTimer(TimeSpan.FromSeconds(5), DispatcherPriority.Normal, Poll, this.Dispatcher);
            InitializeComponent();
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.FrameworkElement.Initialized"/> event. This method is
        /// invoked whenever <see cref="P:System.Windows.FrameworkElement.IsInitialized"/> is set to true internally.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.RoutedEventArgs"/> that contains the event data.</param>
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            InitializeItems();
        }

        /// <summary>
        /// Prepares the list of instructions for display.
        /// </summary>
        private void InitializeItems()
        {
            this.opcodes.AddRange(InstructionSet.Opcodes.Select(i => new StatisticMonitor(i)));
            this.instructionList.ItemsSource = this.opcodes;
            this.timer.Start();
        }
        /// <summary>
        /// Polls all of the opcodes in the collection.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Poll(object source, EventArgs e)
        {
            foreach(var opcode in this.opcodes)
                opcode.Poll();
        }

        /// <summary>
        /// Provides instruction statistic property change notifications.
        /// </summary>
        private sealed class StatisticMonitor : INotifyPropertyChanged
        {
            private static readonly PropertyChangedEventArgs TotalCallsChangedArgs = new PropertyChangedEventArgs("TotalCalls");
            private static readonly PropertyChangedEventArgs AverageTimeChangedArgs = new PropertyChangedEventArgs("AverageTime");

            private readonly OpcodeInfo opcode;
            private long lastCalls;
            private double lastAverage;

            /// <summary>
            /// Initializes a new instance of the <see cref="StatisticMonitor"/> class.
            /// </summary>
            /// <param name="opcode">The opcode.</param>
            public StatisticMonitor(OpcodeInfo opcode)
            {
                this.opcode = opcode;
            }

            /// <summary>
            /// Occurs when a property value changes.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;

            /// <summary>
            /// Gets the name of the opcode.
            /// </summary>
            public string Name
            {
                get { return this.opcode.ToString(); }
            }
            /// <summary>
            /// Gets the total number of opcode calls.
            /// </summary>
            public long? TotalCalls
            {
                get
                {
                    if(this.opcode.Statistics.Include)
                        return this.opcode.Statistics.TotalCalls;
                    else
                        return null;
                }
            }
            /// <summary>
            /// Gets the average time spent in each opcode call.
            /// </summary>
            public double? AverageTime
            {
                get
                {
                    if(this.opcode.Statistics.Include)
                        return this.opcode.Statistics.AverageTime;
                    else
                        return null;
                }
            }

            /// <summary>
            /// Polls for property value changes.
            /// </summary>
            public void Poll()
            {
                if(this.opcode.Statistics.TotalCalls != this.lastCalls)
                {
                    this.lastCalls = this.opcode.Statistics.TotalCalls;
                    OnPropertyChanged(TotalCallsChangedArgs);
                }

                if(this.opcode.Statistics.AverageTime != this.lastAverage)
                {
                    this.lastAverage = this.opcode.Statistics.AverageTime;
                    OnPropertyChanged(AverageTimeChangedArgs);
                }
            }

            /// <summary>
            /// Raises the <see cref="E:PropertyChanged"/> event.
            /// </summary>
            /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
            private void OnPropertyChanged(PropertyChangedEventArgs e)
            {
                var handler = this.PropertyChanged;
                if(handler != null)
                    handler(this, e);
            }
        }
    }
}
